package qj.tool.gae.db;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import net.sf.json.JSONArray;
import net.sf.json.JSONNull;
import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;
import qj.util.Cols;
import qj.util.LangUtil;
import qj.util.ReflectUtil;
import qj.util.funct.P2;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

@SuppressWarnings({"rawtypes","unchecked"})
public class JsonUtil {
	public static interface JSONConverter<A> {
		A mto(JSONObject m);
		Map otm(A a);
	}
	
	public static <A> JSONConverter<A> analyze(final Class<A> clazz) {
		
		final HashMap<String,Field> fields = ReflectUtil.getAllFields(clazz);
		
		final List<P2<Object, Map>> otmTasks = new LinkedList<P2<Object, Map>>();
		final List<P2<JSONObject, Object>> mtoTasks = new LinkedList<P2<JSONObject, Object>>();
		analyze(fields, otmTasks, mtoTasks);

		return new JSONConverter<A>() {
			@Override
			public A mto(JSONObject m) {
				A o = ReflectUtil.newInstance(clazz);
				for (P2<JSONObject,Object> p2 : mtoTasks) {
					try {
						p2.e(m, o);
					} catch (Exception e) {
						throw new RuntimeException("" + m,e);
					}
				}
				return o;
			}
			@Override
			public Map otm(A o) {
				HashMap m = new HashMap();
				for (P2<Object,Map> p2 : otmTasks) {
					p2.e(o,m);
				}
				return m;
			}
		};
	}
	

	public static void analyze(HashMap<String, Field> fields,
			List<P2<Object, Map>> otmTasks,
			List<P2<JSONObject, Object>> mtoTasks) {
		for (Field field : fields.values()) {
			P2<Object, Map> otm = otm(field);
			if (otm != null) {
				otmTasks.add(otm);
			}
			mtoTasks.add(mto(field));
		}
	}
	
	private static P2<Object, Map> otm(final Field field) {
		if (field.getType().isPrimitive()
				|| Number.class.isAssignableFrom(field.getType())
				|| String.class.equals(field.getType())) {
			return new P2<Object, Map>() {public void e(Object o, Map m) {
				m.put(field.getName(), ReflectUtil.getFieldValue(field, o));
			}};
		}
		
		if (field.getType().equals(byte[].class)) {
			return new P2<Object, Map>() {public void e(Object o, Map m) {
				byte[] value = (byte[]) ReflectUtil.getFieldValue(field, o);
				m.put(field.getName(), value==null ? null : value.length);
			}};
		}
		if (field.getType().equals(Date.class)) {
			return new P2<Object, Map>() {public void e(Object o, Map m) {
				Date timevalue = (Date) ReflectUtil.getFieldValue(field, o);
				
				if (timevalue!=null) {
					HashMap<String, Object> value = new HashMap<String, Object>();
					value.put("time", timevalue.getTime());
					value.put("type", "Date");
					m.put(field.getName(), value);
				}
			}};
		}

		if (field.getType().equals(Key.class)) {
			return new P2<Object, Map>() {public void e(Object o, Map m) {
				Key value = (Key) ReflectUtil.getFieldValue(field, o);
				if (value != null) {
					m.put(field.getName(), value.toString());
				}
			}};
		}

		if (List.class.isAssignableFrom(field.getType())) {
			return new P2<Object, Map>() {public void e(Object o, Map m) {
				List list = (List) ReflectUtil.getFieldValue(field, o);
				
				if (list!=null) {
					List value = new LinkedList();
					for (Object object : list) {
						value.add(map(object));
					}
					m.put(field.getName(), value);
				}
			}};
		}
		if (Map.class.isAssignableFrom(field.getType())) {
			return new P2<Object, Map>() {public void e(Object o, Map m) {
				Map map = (Map) ReflectUtil.getFieldValue(field, o);
				
				if (map!=null) {
					Map value = new HashMap();
					Set<Entry> entrySet = map.entrySet();
					for (Entry entry : entrySet) {
						value.put(map(entry.getKey()),map(entry.getValue()));
					}
					m.put(field.getName(), value);
				}
			}};
		}
		return new P2<Object, Map>() {public void e(Object o, Map m) {
			m.put(field.getName(), map(ReflectUtil.getFieldValue(field, o)));
		}};
	}

	

	private static P2<JSONObject, Object> mto(final Field field) {
		return new P2<JSONObject, Object>() {public void e(JSONObject m, Object o) {

			Object value = m.get(field.getName());
			
			if (value == null) {
				// Let it be
			} else if (byte[].class.equals(field.getType())) {
				// JSON can cater for byte[]
				return;
			} else if (value instanceof JSONNull) {
				value = null;
			} else if (field.getType().equals(Date.class)) {
				value = new Date(((JSONObject)value).getLong("time"));

			} else if (field.getType().equals(Key.class)) {
				String strKey = (String) m.getString(field.getName());
				value = AEDB.stringToKey(strKey);
				
			} else if (field.getType().equals(String.class)) {
				value = m.getString(field.getName());
				
			} else if (field.getType().equals(Long.class)) {
				value = m.getLong(field.getName());
				
			} else if (Collection.class.isAssignableFrom(field.getType())) {
				Collection col = (Collection) ReflectUtil.newInstance(field.getType());
				col.addAll((JSONArray)value);
				value = col;
			}
			
			ReflectUtil.setFieldValue(toJava(value, field.getType()), field, o);
		}};
	}


	public static HashMap<Class, JSONConverter> jsonConverters = new HashMap<Class, JSONConverter>();
	public static List<Map> mapAll(List<?> all) {
		LinkedList list = new LinkedList();
		
		for (Object object : all) {
			Object map = map(object);
			list.add(map);
		}
		
		return list;
	}


	public static Object map(Object object) {
		if (object==null) {
			return null;
		} else if (object.getClass().isPrimitive()
				|| Number.class.isAssignableFrom(object.getClass())
				|| String.class.equals(object.getClass())) {
			return object;
		}
		JSONConverter jsonConverter = jsonConverter(object.getClass());
		return jsonConverter.otm(object);
	}

	private static <A> JSONConverter<A> jsonConverter(Class<A> clazz) {
		JSONConverter<A> converter = jsonConverters.get(clazz);
		if (converter == null) {
			converter = analyze(clazz);
			jsonConverters.put(clazz, converter);
		}
		return converter;
	}


	public static String serializeList(List<?> list) {
		return JSONSerializer.toJSON(mapAll(list)).toString();
	}


	public static <A> List<A> toList(String string,Class<A> clazz) {
		LinkedList<A> ret = new LinkedList<A>();
		JSONConverter<A> converter = jsonConverter(clazz);
		
		JSONArray jsonArray = (JSONArray)JSONSerializer.toJSON(string);
		for (int i = 0; i < jsonArray.size(); i++) {
			JSONObject jsonObject = jsonArray.getJSONObject(i);
			try {
				ret.add(converter.mto(jsonObject));
			} catch (RuntimeException e) {
				System.out.println("Can not convert: " + jsonObject);
				throw e;
			}
		}
		
		return ret;
	}
	public static <A> Map<String,A> toMap(String string,Class clazz) {
		Map<String,A> ret = new HashMap<String,A>();
		
		JSONObject jsonMap = (JSONObject)JSONSerializer.toJSON(string);
		Set<Entry> entrySet = jsonMap.entrySet();
		for (Entry entry : entrySet) {
			ret.put((String) entry.getKey(), (A) toJava(entry.getValue(), clazz));
		}
		return ret;
	}

	public static List<Long> toLongs(String string) {
		JSONArray jsonArray = (JSONArray)JSONSerializer.toJSON(string);
		LinkedList<Long> ret = new LinkedList<Long>();
		for (int i = 0; i < jsonArray.size(); i++) {
			ret.add(jsonArray.getLong(i));
		}
		return ret;
	}

	public static <A> A toJava(Object o, Class<A> clazz) {
		if (o==null) {
			return null;
		} else if (clazz.isInstance(o)) {
			return (A) o;
		} else if (o instanceof String) {
			return toJava((String) o, clazz);
		} else if (o instanceof JSONObject) {
			return toJava((JSONObject)o, clazz);
		} else if (o instanceof JSONNull) {
			return null;
		} else if (o instanceof Number && Number.class.isAssignableFrom(clazz)) {
			return LangUtil.convert(o, clazz);
		} else {
			System.out.println(o.getClass() + " -> " + clazz.getName());
			throw new RuntimeException();
		}
	}

	public static <A> A toJava(String string, Class<A> clazz) {
		JSONObject jsonObject = (JSONObject)JSONSerializer.toJSON(string);
		return toJava(jsonObject, clazz);
	}


	private static <A> A toJava(JSONObject jsonObject, Class<A> clazz) {
		JSONConverter<A> converter = jsonConverter(clazz);
		return converter.mto(jsonObject);
	}


	public static String serialize(Object o) {
		if (o==null) {
			return null;
		}
		if (o instanceof Map) {
			return JSONSerializer.toJSON(o).toString();
		}
		
		JSONConverter converter = jsonConverter(o.getClass());
		return JSONSerializer.toJSON(converter.otm(o)).toString();
	}
	
	public static void main(String[] args) {
		System.out.println(serialize(Cols.map("a","b")));
	}
}
